package org.mech.sefer.service.rmi;

import java.util.List;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

/**
 * <p>
 * The AbstractRmiBeanDefinitionRegistrator is abstract registrator for bean
 * definition regarding rmi stuff. It has both client and server
 * implementations, that provide different beans from common sevice definition
 * into context.
 * </p>
 * <p>
 * Date: 24.5.2012 13:02:54
 * </p>
 * 
 * @author martin.krajc
 */
public abstract class AbstractRmiBeanDefinitionRegistrator implements InitializingBean, BeanDefinitionRegistryPostProcessor {
	/**
	 * The logger.
	 */
	private final Logger logger = LoggerFactory.getLogger(AbstractRmiBeanDefinitionRegistrator.class);

	/**
	 * The properties.
	 */
	private Properties properties;

	/**
	 * The port.
	 */
	private Integer rmiRegistryPort;

	/**
	 * The port.
	 */
	private String host;

	/**
	 * The service list.
	 */
	protected List<Class<?>> services;

	/**
	 * Prepare bean definition from service interface.
	 * 
	 * @param serviceInterface
	 *            the service interface
	 * @param beanName
	 *            the bean name of implementation of service in context.
	 * @return the bean definition
	 */
	protected abstract BeanDefinition prepareBeanDefinition(Class<?> serviceInterface, String beanName, String serviceName);

	/**
	 * Prepare bean id in spring context
	 * 
	 * @param serviceInterface
	 *            the service interface
	 * @param beanName
	 *            the bean name
	 * @return the string
	 */
	protected abstract String prepareBeanId(Class<?> serviceInterface, String beanName);

	@Override
	public void afterPropertiesSet() throws Exception {
		Validate.notNull(properties);
		
		final String portString = properties.getProperty(RmiConstants.RMI_PORT);
		
		Validate.notNull(portString, "rmi port is not set");
		
		this.rmiRegistryPort = Integer.valueOf(portString);
		this.host = properties.getProperty(RmiConstants.RMI_HOST);

		Validate.notNull(host);
		Validate.notNull(rmiRegistryPort);
	}

	/**
	 * Generate bean name from service class
	 * 
	 * @param serviceClass
	 *            the service class
	 * @return the string
	 */
	private final String generateBeanName(Class<?> serviceClass) {
		return StringUtils.uncapitalize(serviceClass.getSimpleName());
	}

	/**
	 * Generate service name.
	 * 
	 * @param serviceClass
	 *            the service class
	 * @return the string
	 */
	private final String generateServiceName(Class<?> serviceClass) {
		return "S" + serviceClass.getSimpleName();
	}

	/**
	 * Gets the port.
	 * 
	 * @return the port
	 */
	public String getHost() {
		return host;
	}

	/**
	 * Gets the properties.
	 * 
	 * @return the properties
	 */
	public Properties getProperties() {
		return properties;
	}

	/**
	 * Gets the port.
	 * 
	 * @return the port
	 */
	public Integer getRmiRegistryPort() {
		return rmiRegistryPort;
	}

	/**
	 * Gets the service list.
	 * 
	 * @return the service list
	 */
	public List<Class<?>> getServices() {
		return services;
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		for (Class<?> service : services) {
			final String beanName = generateBeanName(service);

			final BeanDefinition beanDefinition = prepareBeanDefinition(service, beanName, generateServiceName(service));
			final String beanId = prepareBeanId(service, beanName);

			try {
				registry.registerBeanDefinition(beanId, beanDefinition);
				logger.info("registering bean definition '" + beanId + "'");
			} catch (Exception e) {
				logger.error("cannot register bean '" + beanId + "'", e);
			}
		}
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
		// nothing to do
	}

	/**
	 * Sets the properties.
	 * 
	 * @param properties
	 *            the new properties
	 */
	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	/**
	 * Sets the service list.
	 * 
	 * @param services
	 *            the new service list
	 */
	public void setServices(List<Class<?>> services) {
		this.services = services;
	}

}
